﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Text.RegularExpressions;
using System.Reflection;
using System.Runtime.Serialization;

namespace MudEngine.Utils
{
	/// <summary>
	/// Summary description for RegexScanner.
	/// </summary>
	public class RegexScanner
	{
		/// <summary>
		/// 
		/// </summary>
		protected readonly Dictionary<string, string> typePatterns;
		/// <summary>
		/// 
		/// </summary>
		private static RegexScanner _instance;
		/// <summary>
		/// 
		/// </summary>
		private RegexScanner()
		{
			typePatterns = new Dictionary<string, string>();

			typePatterns.Add("string", @"[\w\d\S]+");
			typePatterns.Add("Int16", @"-[0-9]+|[0-9]+");
			typePatterns.Add("UInt16", @"[0-9]+");
			typePatterns.Add("Int32", @"-[0-9]+|[0-9]+");
			typePatterns.Add("UInt32", @"[0-9]+");
			typePatterns.Add("Int64", @"-[0-9]+|[0-9]+");
			typePatterns.Add("UInt64", @"[0-9]+");
			typePatterns.Add("Single", @"[-|+]?[0-9]*[.]?[0-9]*");
			typePatterns.Add("Double", @"[-|+]?[0-9]*[.]?[0-9]*");
			typePatterns.Add("Boolean", @"true|false");
			typePatterns.Add("Byte", @"[0-9]{1,3}");
			typePatterns.Add("SByte", @"-[0-9]{1,3}|[0-9]{1,3}");
			typePatterns.Add("Char", @"[\w\S]{1}");
			typePatterns.Add("Decimal", @"[-|+]?[0-9]*[.]?[0-9]*");
			typePatterns.Add("string_last", @"[\w\d].*");
		}
		/// <summary>
		/// 
		/// </summary>
		private static RegexScanner Instance
		{
			get
			{
				if (_instance == null)
					_instance = new RegexScanner();
				return _instance;
			}
		}

		/// <summary>
		/// Scan memics scanf.
		/// A master regular expression pattern is created that will group each "word" in the text and using regex grouping
		/// extract the values for the field specifications.
		/// Example text: "Hello true 6.5"  fieldSpecification: "{String} {Boolean} {Double}"
		/// The fieldSpecification will result in the generation of a master Pattern:
		/// ([\w\d\S]+)\s+(true|false)\s+([-]|[.]|[-.]|[0-9][0-9]*[.]*[0-9]+)
		/// This masterPattern is ran against the text string and the groups are extracted.
		/// </summary>
		/// <param name="text"></param>
		/// <param name="fieldSpecification">A string that may contain simple field specifications of the form {Int16}, {String}, etc</param>
		/// <returns>object[] that contains values for each field</returns>
		public static object[] Scan(string text, string fieldSpecification)
		{
			object[] targets = null;
			try
			{
				ArrayList targetMatchGroups = new ArrayList();
				ArrayList targetTypes = new ArrayList();

				string matchingPattern = "";
				Regex reggie = null;
				MatchCollection matches = null;

				//masterPattern is going to hold a "big" regex pattern that will be ran against the original text
				string masterPattern = fieldSpecification.Trim();
				matchingPattern = @"(\S+)";
				masterPattern = Regex.Replace(masterPattern, matchingPattern, "($1)"); //insert grouping parens

				//store the group location of the format tags so that we can select the correct group values later.
				matchingPattern = @"(\([\w\d\S]+\))";
				reggie = new Regex(matchingPattern);
				matches = reggie.Matches(masterPattern);
				for (int i = 0; i < matches.Count; i++)
				{
					Match m = matches[i];
					Capture c = m.Groups[1].Captures[0];
					string sVal = c.Value;

					//is this value a {n} value. We will determine this by checking for {
					if (sVal.IndexOf('{') >= 0)
					{
						targetMatchGroups.Add(i);
						if ((i + 1) < matches.Count)
						{
							string p = @"\(\{(\w*)\}\)";	//pull out the type
							sVal = Regex.Replace(sVal, p, "$1");
							targetTypes.Add(sVal);
						}
						else
						{
							masterPattern = masterPattern.Substring(0, c.Index) + "({string_last})" + masterPattern.Substring(c.Index + c.Length, masterPattern.Length - (c.Index + c.Length));
							targetTypes.Add("string_last");
						}
					}
				}

				//Replace all of the types with the pattern that matches that type
				foreach(string pattern in Instance.typePatterns.Keys)
					masterPattern = Regex.Replace(masterPattern, "\\{" + pattern + "\\}", Instance.typePatterns[pattern]);

				masterPattern = Regex.Replace(masterPattern, @"\s+", "\\s+");	//replace the white SPACE with the pattern for white SPACE

				//run our generated pattern against the original text.
				reggie = new Regex(masterPattern);
				matches = reggie.Matches(text);
				//PrintMatches(matches);

				//allocate the targets
				targets = new object[targetMatchGroups.Count];
				int x = 0;
				for (x = 0; x < targetMatchGroups.Count && matches.Count > 0; x++)
				{
					int i = (int)targetMatchGroups[x];
					string tName = (string)targetTypes[x];
					if (i < matches[0].Groups.Count)
					{
						//add 1 to i because i is a result of serveral matches each resulting in one group.
						//this query is one match resulting in serveral groups.
						string sValue = matches[0].Groups[i + 1].Captures[0].Value;
						targets[x] = ReturnValue(tName, sValue);
					}
				}
				if (x == 0)
					targets = new object[] { text };
			}
			catch (Exception ex)
			{
				throw new ScanExeption("Scan exception", ex);
			}

			return targets;
		}//Scan

		/// Scan memics scanf.
		/// A master regular expression pattern is created that will group each "word" in the text and using regex grouping
		/// extract the values for the field specifications.
		/// Example text: "Hello true 6.5"  fieldSpecification: "{0} {1} {2}" and the target array has objects of these types: "String, ,Boolean, Double"
		/// The targets are scanned and each target type is extracted in order to build a master pattern based on these types
		/// The fieldSpecification and target types will result in the generation of a master Pattern:
		/// ([\w\d\S]+)\s+(true|false)\s+([-]|[.]|[-.]|[0-9][0-9]*[.]*[0-9]+)
		/// This masterPattern is ran against the text string and the groups are extracted and placed back into the targets
		/// <param name="text"></param>
		/// <param name="fieldSpecification"></param>
		/// <param name="targets"></param>
		public static void Scan(string text, string fieldSpecification, params object[] targets)
		{
			try
			{
				ArrayList targetMatchGroups = new ArrayList();

				string matchingPattern = "";
				Regex reggie = null;
				MatchCollection matches = null;

				//masterPattern is going to hold a "big" regex pattern that will be ran against the original text
				string masterPattern = fieldSpecification.Trim();
				matchingPattern = @"(\S+)";
				masterPattern = Regex.Replace(masterPattern, matchingPattern, "($1)");		//insert grouping parens

				//store the group location of the format tags so that we can select the correct group values later.
				matchingPattern = @"(\([\w\d\S]+\))";
				reggie = new Regex(matchingPattern);
				matches = reggie.Matches(masterPattern);
				for (int i = 0; i < matches.Count; i++)
				{
					Match m = matches[i];
					string sVal = m.Groups[1].Captures[0].Value;

					//is this value a {n} value. We will determine this by checking for {
					if (sVal.IndexOf('{') >= 0)
					{
						targetMatchGroups.Add(i);
					}
				}

				matchingPattern = @"(\{\S+\})";	//match each paramter tag of the format {n} where n is a digit
				reggie = new Regex(matchingPattern);
				matches = reggie.Matches(masterPattern);

				for (int i = 0; i < targets.Length && i < matches.Count; i++)
				{
					string groupID = String.Format("${0}", (i + 1));
					string innerPattern = "";

					Type t = targets[i].GetType();
					if ( (i + 1) < targets.Length && (i + 1) < matches.Count)
						innerPattern = ReturnPattern(t.Name);
					else
						innerPattern = ReturnPattern("string_last");

					//replace the {n} with the type's pattern
					string groupPattern = "\\{" + i + "\\}";
					masterPattern = Regex.Replace(masterPattern, groupPattern, innerPattern);
				}

				masterPattern = Regex.Replace(masterPattern, @"\s+", "\\s+");	//replace white SPACE with the whitespace pattern

				//run our generated pattern against the original text.
				reggie = new Regex(masterPattern);
				matches = reggie.Matches(text);
				for (int x = 0; x < targetMatchGroups.Count; x++)
				{
					int i = (int)targetMatchGroups[x];
					if (i < matches[0].Groups.Count)
					{
						//add 1 to i because i is a result of serveral matches each resulting in one group.
						//this query is one match resulting in serveral groups.
						string sValue = matches[0].Groups[i + 1].Captures[0].Value;
						Type t = targets[x].GetType();
						targets[x] = ReturnValue(t.Name, sValue);
					}
				}
			}
			catch (Exception ex)
			{
				throw new ScanExeption("Scan exception", ex);
			}
		}	//Scan

		/// <summary>
		/// Return the Value inside of an object that boxes the built in type or references the string
		/// </summary>
		/// <param name="typeName"></param>
		/// <param name="sValue"></param>
		/// <returns></returns>
		private static object ReturnValue(string typeName, string sValue)
		{
			if ("string".Equals(typeName) || "string_last".Equals(typeName))
				return sValue;

			typeName = typeName.Substring(0, 1).ToUpper() + typeName.Substring(1);
			Type type = Type.GetType("System." + typeName);
			return type.InvokeMember(
				"Parse",
				BindingFlags.Public | BindingFlags.Static | BindingFlags.InvokeMethod,
				null,
				null,
				new object[]{sValue});
		}

		/// <summary>
		/// Return a pattern for regular expressions that will match the built in type specified by name
		/// </summary>
		/// <param name="typeName"></param>
		/// <returns></returns>
		private static string ReturnPattern(string typeName)
		{
			return Instance.typePatterns[typeName];
		}

		static void PrintMatches(MatchCollection matches)
		{
			Console.WriteLine("===---===---===---===");
			int matchCount = 0;
			Console.WriteLine("Match Count = " + matches.Count);
			foreach (Match m in matches)
			{
				if (m == Match.Empty) Console.WriteLine("Empty match");
				Console.WriteLine("Match" + (++matchCount));
				for (int i = 0; i < m.Groups.Count; i++)
				{
					Group g = m.Groups[i];
					Console.WriteLine("Group" + i + "='" + g + "'");
					CaptureCollection cc = g.Captures;
					for (int j = 0; j < cc.Count; j++)
					{
						Capture c = cc[j];
						System.Console.Write("Capture" + j + "='" + c + "', Position=" + c.Index + "   <");
						for (int k = 0; k < c.ToString().Length; k++)
						{
							Console.Write(((Int32)(c.ToString()[k])));
						}
						Console.WriteLine(">");
					}
				}
			}
		}
	}

	/// <summary>
	/// Exceptions that are thrown by this namespace and the Scanner Class
	/// </summary>
	class ScanExeption : Exception
	{
		public ScanExeption()
			: base()
		{
		}

		public ScanExeption(string message)
			: base(message)
		{
		}

		public ScanExeption(string message, Exception inner)
			: base(message, inner)
		{
		}

		public ScanExeption(SerializationInfo info, StreamingContext context) : base(info, context)
		{
		}
	}
}
